// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。
// 
// 内联工具进行两次传递：首先，caninl确定哪些
// 函数适合内联，对于那些函数，
// 保存正文的副本。然后InlineCalls将每个函数体带到
// 展开对Inlineable函数的调用。
// 
// 调试。l旗控制着进攻性。注意main（）交换级别0和1，
// 使1成为默认值，并禁用-l。附加级别（超过-l）可能有问题，
// 不受支持。
// 0:禁用
// 1:80个节点叶函数、OneLiner、恐慌、，懒惰类型检查（默认值）
// 2:（未指定）
// 3:（未指定）
// 4:允许非叶函数
// 
// 在某个时候，这可能会得到另一个默认值，并通过-N关闭。
// d typcheckinl标志允许对所有导入的实体进行早期类型检查，
// 这对清除bug很有用。
// 
// 调试。m标志启用诊断输出。单个-m用于验证
// 哪个调用是内联的还是非内联的，更多用于调试，并且在任何时候都可能消失。

package inline

import (
	"fmt"
	"go/constant"
	"strings"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/logopt"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// 内联预算参数，集中在一个地方
const (
	inlineMaxBudget       = 80
	inlineExtraAppendCost = 0
	// 如果最多有一个调用，则默认为内联-l=4通过使用1来替代它。
	inlineExtraCallCost  = 57              // 57以提供最大效益为基准，没有任何不好的意外；参见https:
	inlineExtraPanicCost = 1               // 不要惩罚内联恐慌。
	inlineExtraThrowCost = inlineMaxBudget // 当前（2018-05/1.11）代码，内联运行时。扔也没用。

	inlineBigFunctionNodes   = 5000 // 有这么多节点的函数被认为是“大的”。
	inlineBigFunctionMaxCost = 20   // 当内联到一个“大”函数时，内联的最大成本。
)

// InlinePackage查找可以内联的函数，并在walk展开它们之前对它们进行克隆。
func InlinePackage() {
	ir.VisitFuncsBottomUp(typecheck.Target.Decls, func(list []*ir.Func, recursive bool) {
		numfns := numNonClosures(list)
		for _, n := range list {
			if !recursive || numfns > 1 {
				// 如果没有
				// 递归，或者递归周期是
				// 跨多个函数，则允许内联。
				CanInline(n)
			} else {
				if base.Flag.LowerM > 1 {
					fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(n), n.Nname)
				}
			}
			InlineCalls(n)
		}
	})
}

// CanInline确定fn是否可内联。
// 如果是这样，CanInline将保存fn的副本。身体和fn。fn中的Dcl。Inl。
// fn和fn。尸体已经过打字检查了。
func CanInline(fn *ir.Func) {
	if fn.Nname == nil {
		base.Fatalf("CanInline no nname %+v", fn)
	}

	var reason string // 如果有，说明函数未内联的原因
	if base.Flag.LowerM > 1 || logopt.Enabled() {
		defer func() {
			if reason != "" {
				if base.Flag.LowerM > 1 {
					fmt.Printf("%v: cannot inline %v: %s\n", ir.Line(fn), fn.Nname, reason)
				}
				if logopt.Enabled() {
					logopt.LogOpt(fn.Pos(), "cannotInlineFunction", "inline", ir.FuncName(fn), reason)
				}
			}
		}()
	}

	// 如果标记为“go:noinline”，则不内联
	if fn.Pragma&ir.Noinline != 0 {
		reason = "marked go:noinline"
		return
	}

	// 如果标记为“go:norace”和-race编译，则不内联。
	if base.Flag.Race && fn.Pragma&ir.Norace != 0 {
		reason = "marked go:norace with -race compilation"
		return
	}

	// 如果标记为“go:nocheckptr”和-d checkptr编译，则不要内联。
	if base.Debug.Checkptr != 0 && fn.Pragma&ir.NoCheckPtr != 0 {
		reason = "marked go:nocheckptr"
		return
	}

	// 如果标记为“go:cgo_unsafe_args”，则不要内联，因为
	// 函数会对其参数框架布局进行假设。
	if fn.Pragma&ir.CgoUnsafeArgs != 0 {
		reason = "marked go:cgo_unsafe_args"
		return
	}

	// 如果标记为“go:uintpttrescapes”，则不要内联，因为在内联过程中会丢失转义信息。
	if fn.Pragma&ir.UintptrEscapes != 0 {
		reason = "marked as having an escaping uintptr argument"
		return
	}

	// nowritebarrierrec检查器目前在函数
	// 粒度上工作，因此内联yeswritebarrierrec函数可能会将其混淆（#22342）。作为一种解决方法，暂时不允许内联
	// 它们。
	if fn.Pragma&ir.Yeswritebarrierrec != 0 {
		reason = "marked go:yeswritebarrierrec"
		return
	}

	// 如果fn没有主体（在Go之外定义），则无法将其内联。
	if len(fn.Body) == 0 {
		reason = "no function body"
		return
	}

	if fn.Typecheck() == 0 {
		base.Fatalf("CanInline on non-typechecked function %v", fn)
	}

	n := fn.Nname
	if n.Func.InlinabilityChecked() {
		return
	}
	defer n.Func.SetInlinabilityChecked(true)

	cc := int32(inlineExtraCallCost)
	if base.Flag.LowerL == 4 {
		cc = 1 // 这似乎比0产生更好的性能。
	}

	// 在游戏的这一点上，我们正在研究的函数可能会有“过时”的汽车，变量仍然出现在Dcl列表中，但
	// 在函数体中不再有任何用途（由于
	// 通过死码消除）。在创建下面的“Inline.Dcl”字段时，我们希望排除这些死变量
	// ；为了实现这一点，
	// 下面的hairyVisitor构建了一个使用/引用的
	// 本地人的地图，我们使用这个地图生成一个经过修剪的内联图。Dcl 
	// 列表。详见第25249期。

	visitor := hairyVisitor{
		budget:        inlineMaxBudget,
		extraCallCost: cc,
	}
	if visitor.tooHairy(fn) {
		reason = visitor.reason
		return
	}

	n.Func.Inl = &ir.Inline{
		Cost: inlineMaxBudget - visitor.budget,
		Dcl:  pruneUnusedAutos(n.Defn.(*ir.Func).Dcl, &visitor),
		Body: inlcopylist(fn.Body),

		CanDelayResults: canDelayResults(fn),
	}

	if base.Flag.LowerM > 1 {
		fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, inlineMaxBudget-visitor.budget, fn.Type(), ir.Nodes(n.Func.Inl.Body))
	} else if base.Flag.LowerM != 0 {
		fmt.Printf("%v: can inline %v\n", ir.Line(fn), n)
	}
	if logopt.Enabled() {
		logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", inlineMaxBudget-visitor.budget))
	}
}

// canDelayResults报告对fn的内联调用是否会延迟
// 在“return”语句之前声明结果参数。
func canDelayResults(fn *ir.Func) bool {
	// 如果：
	// （1）内联函数中只有一个“return”语句，我们可以延迟声明+初始化结果参数；
	// /（2）这不是一个空的返回语句（#44355）；
	// /（3）结果参数未命名。

	nreturns := 0
	ir.VisitList(fn.Body, func(n ir.Node) {
		if n, ok := n.(*ir.ReturnStmt); ok {
			nreturns++
			if len(n.Results) == 0 {
				nreturns++ // 空返回语句（案例2）
			}
		}
	})

	if nreturns != 1 {
		return false // 不完全是一个返回语句（案例1）
	}

	// 返回值的临时值。
	for _, param := range fn.Type().Results().FieldSlice() {
		if sym := types.OrigSym(param.Sym); sym != nil && !sym.IsBlank() {
			return false // 找到一个命名的结果参数（案例3）
		}
	}

	return true
}

// hairyVisitor访问函数体以确定其内联
// hairyVisitor以及它是否可以内联。
type hairyVisitor struct {
	budget        int32
	reason        string
	extraCallCost int32
	usedLocals    ir.NameSet
	do            func(ir.Node) bool
}

func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
	v.do = v.doNode // 缓存关闭
	if ir.DoChildren(fn, v.do) {
		return true
	}
	if v.budget < 0 {
		v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-v.budget, inlineMaxBudget)
		return true
	}
	return false
}

func (v *hairyVisitor) doNode(n ir.Node) bool {
	if n == nil {
		return false
	}
	switch n.Op() {
	// 如果可以内联调用，那么调用是可以的，我们有身体的预算。
	case ir.OCALLFUNC:
		n := n.(*ir.CallExpr)
		// 调用运行时的函数。getcaller{pc，sp}无法内联ABCD EFG
		// 
		// 运行时。throw是一个“廉价调用”，就像普通代码中的恐慌一样。
		if n.X.Op() == ir.ONAME {
			name := n.X.(*ir.Name)
			if name.Class == ir.PFUNC && types.IsRuntimePkg(name.Sym().Pkg) {
				fn := name.Sym().Name
				if fn == "getcallerpc" || fn == "getcallersp" {
					v.reason = "call to " + fn
					return true
				}
				if fn == "throw" {
					v.budget -= inlineExtraThrowCost
					break
				}
			}
		}
		if n.X.Op() == ir.OMETHEXPR {
			if meth := ir.MethodExprName(n.X); meth != nil {
				if fn := meth.Func; fn != nil {
					s := fn.Sym()
					var cheap bool
					if types.IsRuntimePkg(s.Pkg) && s.Name == "heapBits.nextArena" {
						// 特例：显式允许
						// 运行时的堆栈中内联。一堆。下一步，即使它调用慢路径
						// 运行时。一堆。下塔雷纳。
						cheap = true
					}
					// 特例：在可以进行未对齐加载的架构上，
					// 明确地将编码/二进制方法标记为廉价的
					// 因为在实践中它们是廉价的，尽管我们的内联
					// 预算系统没有看到这一点。见第42958期。
					if base.Ctxt.Arch.CanMergeLoads && s.Pkg.Path == "encoding/binary" {
						switch s.Name {
						case "littleEndian.Uint64", "littleEndian.Uint32", "littleEndian.Uint16",
							"bigEndian.Uint64", "bigEndian.Uint32", "bigEndian.Uint16",
							"littleEndian.PutUint64", "littleEndian.PutUint32", "littleEndian.PutUint16",
							"bigEndian.PutUint64", "bigEndian.PutUint32", "bigEndian.PutUint16":
							cheap = true
						}
					}
					if cheap {
						break // 像对待任何其他节点一样对待，即1个
					}
				}
			}
		}

		if ir.IsIntrinsicCall(n) {
			// 像对待任何其他节点一样对待。
			break
		}

		if fn := inlCallee(n.X); fn != nil && typecheck.HaveInlineBody(fn) {
			v.budget -= fn.Inl.Cost
			break
		}

		// 非叶内联的调用成本。
		v.budget -= v.extraCallCost

	case ir.OCALLMETH:
		base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")

	// 无论预算如何，都有太多问题
	case ir.OCALL, ir.OCALLINTER:
		// 非叶内联的调用成本。
		v.budget -= v.extraCallCost

	case ir.OPANIC:
		n := n.(*ir.UnaryExpr)
		if n.X.Op() == ir.OCONVIFACE && n.X.(*ir.ConvExpr).Implicit() {
			// 黑客继续反思。旗帜必须是可内联的，用于测试内联。
			// 在CL284412之前，这些转换后来在
			// 编译器中引入，因此它们不计入内联预算。
			v.budget++
		}
		v.budget -= inlineExtraPanicCost

	case ir.ORECOVER:
		// recover与参数帧指针匹配，以找到正确的panic值，因此它需要参数帧。
		v.reason = "call to recover"
		return true

	case ir.OCLOSURE:
		if base.Debug.InlFuncsWithClosures == 0 {
			v.reason = "not inlining functions with closures"
			return true
		}

		// TODO（danscales）：可能使预算与闭包数量成比例
		// 变量，例如：
		// v.budget-=int32（len（n.（*ir.ClosureExpr）。Func。ClosureVars）*3）
		v.budget -= 15
		// 扫描关闭主体（DoChildren不会自动
		// 执行），检查主体中是否存在不允许的操作，并将
		// 主体包括在预算中。
		if doList(n.(*ir.ClosureExpr).Func.Body, v.do) {
			return true
		}

	case ir.OSELECT,
		ir.OGO,
		ir.ODEFER,
		ir.ODCLTYPE, // 还不能打印
		ir.OTAILCALL:
		v.reason = "unhandled op " + n.Op().String()
		return true

	case ir.OAPPEND:
		v.budget -= inlineExtraAppendCost

	case ir.ODEREF:
		// /*（*X）（不安全。指针（&X））是低成本
		n := n.(*ir.StarExpr)

		ptr := n.X
		for ptr.Op() == ir.OCONVNOP {
			ptr = ptr.(*ir.ConvExpr).X
		}
		if ptr.Op() == ir.OADDR {
			v.budget += 1 // 撤消ir默认成本的一半。ODEREF+ir。OADDR 
		}

	case ir.OCONVNOP:
		// 这不会生成代码，但孩子们可能会。
		v.budget++ // 撤销默认成本

	case ir.ODCLCONST, ir.OFALL:
		// 这些节点不生成代码；从内联预算中省略。
		return false

	case ir.OIF:
		n := n.(*ir.IfStmt)
		if ir.IsConst(n.Cond, constant.Bool) {
			// 这是一个假设，条件不需要任何费用。
			if doList(n.Init(), v.do) {
				return true
			}
			if ir.BoolVal(n.Cond) {
				return doList(n.Body, v.do)
			} else {
				return doList(n.Else, v.do)
			}
		}

	case ir.ONAME:
		n := n.(*ir.Name)
		if n.Class == ir.PAUTO {
			v.usedLocals.Add(n)
		}

	case ir.OBLOCK:
		// 此时我们应该看到的唯一对象是一个空对象。
		// 无论如何，让下面的访问列表（n.List（））来处理这些语句，
		// 并且不为对象本身收费。++撤销了下面的--。
		v.budget++

	case ir.OMETHVALUE, ir.OSLICELIT:
		v.budget-- // 黑客工具库-cmp。

	case ir.OMETHEXPR:
		v.budget++ // 黑客工具库-cmp。
	}

	v.budget--

	// 调试时，不要过早停止，以获得内联此函数的全部成本
	if v.budget < 0 && base.Flag.LowerM < 2 && !logopt.Enabled() {
		v.reason = "too expensive"
		return true
	}

	return ir.DoChildren(n, v.do)
}

func isBigFunc(fn *ir.Func) bool {
	budget := inlineBigFunctionNodes
	return ir.Any(fn, func(n ir.Node) bool {
		budget--
		return budget <= 0
	})
}

// inlcopylist（连同inlcopy）递归复制节点列表，除了
// 它保持相同的ONAME、OTYPE和OLITERAL节点。用于复制
// 可内联函数的主体和DCL。
func inlcopylist(ll []ir.Node) []ir.Node {
	s := make([]ir.Node, len(ll))
	for i, n := range ll {
		s[i] = inlcopy(n)
	}
	return s
}

// inlcopy类似于DeepCopy（），但需要额外的工作来复制闭包。
func inlcopy(n ir.Node) ir.Node {
	var edit func(ir.Node) ir.Node
	edit = func(x ir.Node) ir.Node {
		switch x.Op() {
		case ir.ONAME, ir.OTYPE, ir.OLITERAL, ir.ONIL:
			return x
		}
		m := ir.Copy(x)
		ir.EditChildren(m, edit)
		if x.Op() == ir.OCLOSURE {
			x := x.(*ir.ClosureExpr)
			// 需要保存/复制x.Func。Nname，
			// x.Func。纳姆。Ntype，x.Func。Dcl，x.Func。ClosureVars和
			// x.Func。用于iexport和本地内联的主体。
			oldfn := x.Func
			newfn := ir.NewFunc(oldfn.Pos())
			m.(*ir.ClosureExpr).Func = newfn
			newfn.Nname = ir.NewNameAt(oldfn.Nname.Pos(), oldfn.Nname.Sym())
			// XXX同意分享fn。类型（）？？
			newfn.Nname.SetType(oldfn.Nname.Type())
			// 对于-G=3模式，Ntype可以为零。
			if oldfn.Nname.Ntype != nil {
				newfn.Nname.Ntype = inlcopy(oldfn.Nname.Ntype).(ir.Ntype)
			}
			newfn.Body = inlcopylist(oldfn.Body)
			// 对Dcl和ClosureVar切片进行浅拷贝
			newfn.Dcl = append([]*ir.Name(nil), oldfn.Dcl...)
			newfn.ClosureVars = append([]*ir.Name(nil), oldfn.ClosureVars...)
		}
		return m
	}
	return edit(n)
}

// InlineCalls/inlnode遍历fn的语句和表达式，并替换对inlineable函数进行的任何
// 调用。这是外部入口点。
func InlineCalls(fn *ir.Func) {
	savefn := ir.CurFunc
	ir.CurFunc = fn
	maxCost := int32(inlineMaxBudget)
	if isBigFunc(fn) {
		maxCost = inlineBigFunctionMaxCost
	}
	// 映射以跟踪已在特定
	// 调用站点内联的函数，以便在再次到达
	// 递归循环的开始时停止内联。我们不直接内联递归函数，
	// 但是如果有许多函数的递归循环，我们允许内联。
	// 最有可能的是，在我们再次进入循环开始之前，内联将停止，但地图捕捉到了不寻常的情况。
	inlMap := make(map[*ir.Func]bool)
	var edit func(ir.Node) ir.Node
	edit = func(n ir.Node) ir.Node {
		return inlnode(n, maxCost, inlMap, edit)
	}
	ir.EditChildren(fn, edit)
	ir.CurFunc = savefn
}

// inlnode在树上递归以查找可内联调用，这将由mkinlcall将
// 转换为OINLCALLs。当递归到来时，备份将检查left、right、list、rlist、ninit、ntest、nincr、
// nbody和nelse，并使用
// 上面的4个inlconv/glue函数之一，将Oincall转换为表达式、语句，或将其修补到该节点列表或rlist中。
// 注意，将glue函数向下传递到创建OilCall的级别是没有意义的，因为它们必须编辑/this/n，所以你也必须向下推这个函数，但是你也可以在这里这样做。所以这更干净，更简单。
// inlnode的结果必须重新分配给n，例如
// n.Left=inlnode（n.Left）
func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
	if n == nil {
		return n
	}

	switch n.Op() {
	case ir.ODEFER, ir.OGO:
		n := n.(*ir.GoDeferStmt)
		switch call := n.Call; call.Op() {
		case ir.OCALLMETH:
			base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
		case ir.OCALLFUNC:
			call := call.(*ir.CallExpr)
			call.NoInline = true
		}
	case ir.OTAILCALL:
		n := n.(*ir.TailCallStmt)
		n.Call.NoInline = true // 暂时不内联尾部调用。也许我们可以像RETURN fn（arg）一样内联它？

	// 在这里（或更早）执行这些操作，
	// 因此逃避分析可以避免更多的堆移动。
	case ir.OCLOSURE:
		return n
	case ir.OCALLMETH:
		base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
	case ir.OCALLFUNC:
		n := n.(*ir.CallExpr)
		if n.X.Op() == ir.OMETHEXPR {
			// 防止内联一些反射。使用checkptr时的值方法，
			// 即使在没有它的情况下编译包reflect（#35073）。
			if meth := ir.MethodExprName(n.X); meth != nil {
				s := meth.Sym()
				if base.Debug.Checkptr != 0 && types.IsReflectPkg(s.Pkg) && (s.Name == "Value.UnsafeAddr" || s.Name == "Value.Pointer") {
					return n
				}
			}
		}
	}

	lno := ir.SetPos(n)

	ir.EditChildren(n, edit)

	// 所有分支都已排除，现在是时候转换此节点本身了，除非被此函数顶部的
	// 开关禁止。
	switch n.Op() {
	case ir.OCALLMETH:
		base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")

	case ir.OCALLFUNC:
		call := n.(*ir.CallExpr)
		if call.NoInline {
			break
		}
		if base.Flag.LowerM > 3 {
			fmt.Printf("%v:call to func %+v\n", ir.Line(n), call.X)
		}
		if ir.IsIntrinsicCall(call) {
			break
		}
		if fn := inlCallee(call.X); fn != nil && typecheck.HaveInlineBody(fn) {
			n = mkinlcall(call, fn, maxCost, inlMap, edit)
		}
	}

	base.Pos = lno

	return n
}

// inlCallee接受一个函数类型的表达式，并在静态已知的情况下返回它所引用的名为
// 的基础函数。否则，它返回nil。
func inlCallee(fn ir.Node) *ir.Func {
	fn = ir.StaticValue(fn)
	switch fn.Op() {
	case ir.OMETHEXPR:
		fn := fn.(*ir.SelectorExpr)
		n := ir.MethodExprName(fn)
		// 检查接收器类型是否与fn匹配。X.
		// TODO（mdempsky）：处理指针接收器参数的隐式解引用
		// ？
		if n == nil || !types.Identical(n.Type().Recv().Type, fn.X.Type()) {
			return nil
		}
		return n.Func
	case ir.ONAME:
		fn := fn.(*ir.Name)
		if fn.Class == ir.PFUNC {
			return fn.Func
		}
	case ir.OCLOSURE:
		fn := fn.(*ir.ClosureExpr)
		c := fn.Func
		CanInline(c)
		return c
	}
	return nil
}

func inlParam(t *types.Field, as ir.InitNode, inlvars map[*ir.Name]*ir.Name) ir.Node {
	if t.Nname == nil {
		return ir.BlankNode
	}
	n := t.Nname.(*ir.Name)
	if ir.IsBlank(n) {
		return ir.BlankNode
	}
	inlvar := inlvars[n]
	if inlvar == nil {
		base.Fatalf("missing inlvar for %v", n)
	}
	as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, inlvar))
	inlvar.Name().Defn = as
	return inlvar
}

var inlgen int

// SSADumpInline使SSA后端在生成用于调试编译器本身的输出时，有机会转储函数
// 。
var SSADumpInline = func(*ir.Func) {}

// NewInline允许重写内联实现。
// 如果返回nil，则旧的内联将处理此调用
// 而不是。
var NewInline = func(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCallExpr { return nil }

// 如果n是OCALLFUNC节点，fn是具有内联体的
// 函数的ONAME节点，则返回一个可以替换n的OINLCALL节点。
// 返回节点的Ninit具有参数赋值，nbbody是
// 内联函数体，并且（List，Rlist）包含（输入，输出）
// 参数。
// mkinlcall的结果必须重新分配给n，例如
// n.Left=mkinlcall（n.Left，fn，isddd）
func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
	if fn.Inl == nil {
		if logopt.Enabled() {
			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc),
				fmt.Sprintf("%s cannot be inlined", ir.PkgFuncName(fn)))
		}
		return n
	}
	if fn.Inl.Cost > maxCost {
		// 内联函数体太大。通常我们使用这个检查来限制
		// 内联到非常大的函数中。见第26546期和第17566期。huang jian defg
		if logopt.Enabled() {
			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc),
				fmt.Sprintf("cost %d of %s exceeds max large caller cost %d", fn.Inl.Cost, ir.PkgFuncName(fn), maxCost))
		}
		return n
	}

	if fn == ir.CurFunc {
		if logopt.Enabled() {
			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(ir.CurFunc)))
		}
		return n
	}

	// 不要内联没有形状参数但在
	// 处传递的函数fn，至少一个形状参数。这意味着我们必须内联一个非泛型函数
	// fn，该函数被传递到泛型函数中，并且可以用形状
	// arg调用，因为它匹配适当的类型参数。但是fn可能包括
	// 一个接口转换（可以应用于形状参数），它在我们第一次创建泛型函数的实例化时并不明显。
	// 如果我们真的进行内联，我们就无法处理这个问题，因为我们想知道所有的接口转换都是在制版后立即进行的。因此，在这种情况下，我们避免使用
	// 内联。见#49309。
	if !fn.Type().HasShape() {
		for _, arg := range n.Args {
			if arg.Type().HasShape() {
				if logopt.Enabled() {
					logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc),
						fmt.Sprintf("inlining non-shape function %v with shape args", ir.FuncName(fn)))
				}
				return n
			}
		}
	}

	if base.Flag.Cfg.Instrumenting && types.IsRuntimePkg(fn.Sym().Pkg) {
		// 不能检测运行时包。
		// 仪器跳过运行时包。然而，一些运行时代码可以被
		// 内联到其他包中，并在其中进行检测。为了避免这种情况，我们在插入指令时禁用了运行时函数的内联。
		// 我们观察到的例子是LockOSThread的内联，
		// 这会导致对m内容的错误竞争报告。
		return n
	}

	if inlMap[fn] {
		if base.Flag.LowerM > 1 {
			fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", ir.Line(n), fn, ir.FuncName(ir.CurFunc))
		}
		return n
	}
	inlMap[fn] = true
	defer func() {
		inlMap[fn] = false
	}()

	typecheck.FixVariadicCall(n)

	parent := base.Ctxt.PosTable.Pos(n.Pos()).Base().InliningIndex()

	sym := fn.Linksym()
	inlIndex := base.Ctxt.InlTree.Add(parent, n.Pos(), sym)

	if base.Flag.GenDwarfInl > 0 {
		if !sym.WasInlined() {
			base.Ctxt.DwFixups.SetPrecursorFunc(sym, fn)
			sym.Set(obj.AttrWasInlined, true)
		}
	}

	if base.Flag.LowerM != 0 {
		fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn)
	}
	if base.Flag.LowerM > 2 {
		fmt.Printf("%v: Before inlining: %+v\n", ir.Line(n), n)
	}

	res := NewInline(n, fn, inlIndex)
	if res == nil {
		res = oldInline(n, fn, inlIndex)
	}

	// 可传递的内联
	// 在导出实体之前最好这样做，但不能在内联已扩展的情况下发射实体。
	// 相反，我们会释放身体需要的东西
	// 每次使用都必须重新内联。幸运的是，这些都很小。
	ir.EditChildren(res, edit)

	if base.Flag.LowerM > 2 {
		fmt.Printf("%v: After inlining %+v\n\n", ir.Line(res), res)
	}

	return res
}

// CalleeEffects附加了从评估被调用方到初始化的任何副作用。
func CalleeEffects(init *ir.Nodes, callee ir.Node) {
	for {
		switch callee.Op() {
		case ir.ONAME, ir.OCLOSURE, ir.OMETHEXPR:
			return // 完成

		case ir.OCONVNOP:
			conv := callee.(*ir.ConvExpr)
			init.Append(ir.TakeInit(conv)...)
			callee = conv.X

		case ir.OINLCALL:
			ic := callee.(*ir.InlinedCallExpr)
			init.Append(ir.TakeInit(ic)...)
			init.Append(ic.Body.Take()...)
			callee = ic.SingleResult()

		default:
			base.FatalfAt(callee.Pos(), "unexpected callee expression: %v", callee)
		}
	}
}

// oldInline创建一个InlinedCallExpr来替换给定的调用
// 表达式。fn是要内联的被调用函数。inlineindex是
// 内联树位置索引，用于src。NewInliningBase 
// 重写位置时。
func oldInline(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCallExpr {
	if base.Debug.TypecheckInl == 0 {
		typecheck.ImportedBody(fn)
	}

	SSADumpInline(fn)

	ninit := call.Init()

	// 对于普通函数调用，函数被调用方表达式
	// 可能包含副作用。确保保存这些，如有必要（#42703）。
	if call.Op() == ir.OCALLFUNC {
		CalleeEffects(&ninit, call.X)
	}

	// 使用临时名称代替原始名称。
	inlvars := make(map[*ir.Name]*ir.Name)

	// 记录表格/本地信息，以便以后进行后期处理
	var inlfvars []*ir.Name

	for _, ln := range fn.Inl.Dcl {
		if ln.Op() != ir.ONAME {
			continue
		}
		if ln.Class == ir.PPARAMOUT { // 返回下面处理的值。
			continue
		}
		inlf := typecheck.Expr(inlvar(ln)).(*ir.Name)
		inlvars[ln] = inlf
		if base.Flag.GenDwarfInl > 0 {
			if ln.Class == ir.PPARAM {
				inlf.Name().SetInlFormal(true)
			} else {
				inlf.Name().SetInlLocal(true)
			}
			inlf.SetPos(ln.Pos())
			inlfvars = append(inlfvars, inlf)
		}
	}

	// 如果返回值为临时值，我们可以延迟声明+初始化结果参数。
	var retvars []ir.Node
	for i, t := range fn.Type().Results().Fields().Slice() {
		var m *ir.Name
		if nn := t.Nname; nn != nil && !ir.IsBlank(nn.(*ir.Name)) && !strings.HasPrefix(nn.Sym().Name, "~r") {
			n := nn.(*ir.Name)
			m = inlvar(n)
			m = typecheck.Expr(m).(*ir.Name)
			inlvars[n] = m
		} else {
			// 匿名返回值，合成用于替换返回的赋值的名称
			m = retvar(t, i)
		}

		if base.Flag.GenDwarfInl > 0 {
			// 不更新src。返回变量上的Pos，如果
			// 是由内联程序制造的（例如“~R2”）；这些变量
			// 不是原始被调用方的一部分。
			if !strings.HasPrefix(m.Sym().Name, "~R") {
				m.Name().SetInlFormal(true)
				m.SetPos(t.Pos)
				inlfvars = append(inlfvars, m)
			}
		}

		retvars = append(retvars, m)
	}

	// 为参数的临时名称分配参数。
	as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil)
	as.Def = true
	if call.Op() == ir.OCALLMETH {
		base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
	}
	as.Rhs.Append(call.Args...)

	if recv := fn.Type().Recv(); recv != nil {
		as.Lhs.Append(inlParam(recv, as, inlvars))
	}
	for _, param := range fn.Type().Params().Fields().Slice() {
		as.Lhs.Append(inlParam(param, as, inlvars))
	}

	if len(as.Rhs) != 0 {
		ninit.Append(typecheck.Stmt(as))
	}

	if !fn.Inl.CanDelayResults {
		// 将返回参数归零。
		for _, n := range retvars {
			ninit.Append(ir.NewDecl(base.Pos, ir.ODCL, n.(*ir.Name)))
			ras := ir.NewAssignStmt(base.Pos, n, nil)
			ninit.Append(typecheck.Stmt(ras))
		}
	}

	retlabel := typecheck.AutoLabel(".i")

	inlgen++

	// 在内联正文前添加一个内联标记。
	// 这个标记在代码中是内联的，所以设置断点是合理的。不确定这是否真的有必要
	// （在这种情况下，它可以放在函数的末尾）。
	// 注意28603号问题。
	ninit.Append(ir.NewInlineMarkStmt(call.Pos().WithIsStmt(), int64(inlIndex)))

	subst := inlsubst{
		retlabel:    retlabel,
		retvars:     retvars,
		inlvars:     inlvars,
		defnMarker:  ir.NilExpr{},
		bases:       make(map[*src.PosBase]*src.PosBase),
		newInlIndex: inlIndex,
		fn:          fn,
	}
	subst.edit = subst.node

	body := subst.list(ir.Nodes(fn.Inl.Body))

	lab := ir.NewLabelStmt(base.Pos, retlabel)
	body = append(body, lab)

	if !typecheck.Go117ExportTypes {
		typecheck.Stmts(body)
	}

	if base.Flag.GenDwarfInl > 0 {
		for _, v := range inlfvars {
			v.SetPos(subst.updatedPos(v.Pos()))
		}
	}

	// 转储列表（“ninit post”，ninit）；

	res := ir.NewInlinedCallExpr(base.Pos, body, retvars)
	res.SetInit(ninit)
	res.SetType(call.Type())
	res.SetTypecheck(1)
	return res
}

// 每次扩展一个函数时，我们都会在调用函数中生成一组新的TMPNAME，
// PAUTO，并将它们与被调用函数的
// PPARAM、PAUTOS和PPARAMOUTs连接起来。
func inlvar(var_ *ir.Name) *ir.Name {
	if base.Flag.LowerM > 3 {
		fmt.Printf("inlvar %+v\n", var_)
	}

	n := typecheck.NewName(var_.Sym())
	n.SetType(var_.Type())
	n.SetTypecheck(1)
	n.Class = ir.PAUTO
	n.SetUsed(true)
	n.SetAutoTemp(var_.AutoTemp())
	n.Curfn = ir.CurFunc // 调用函数，而不是被调用函数
	n.SetAddrtaken(var_.Addrtaken())

	ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n)
	return n
}

// 合成一个变量来存储内联函数的结果。
func retvar(t *types.Field, i int) *ir.Name {
	n := typecheck.NewName(typecheck.LookupNum("~R", i))
	n.SetType(t.Type)
	n.SetTypecheck(1)
	n.Class = ir.PAUTO
	n.SetUsed(true)
	n.Curfn = ir.CurFunc // 调用函数，而不是被调用函数
	ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n)
	return n
}

// inlsubst类型实现单个
// 函数调用的实际内联。
type inlsubst struct {
	// 后藤的目标取代了回报。
	retlabel *types.Sym

	// 临时结果变量。
	retvars []ir.Node

	inlvars map[*ir.Name]*ir.Name
	// defnMarker用于标记要重新分配的节点。
	// inlsubs。clovar在创建新的ONAME时设置了这一点。
	// inlsubs。节点将为inlvar设置正确的Defn。
	defnMarker ir.NilExpr

	// 用一个额外的
	// 内联调用帧将原始PosBase映射到PosBase。
	bases map[*src.PosBase]*src.PosBase

	// newinlineindex是指向
	// insert for inlined nodes的内联调用帧的索引。
	newInlIndex int

	edit func(ir.Node) ir.Node // subst.node方法值闭包的缓存副本

	// 如果非nil，则我们在内联函数内的闭包中，而
	// newclofn是新内联闭包的函数。
	newclofn *ir.Func

	fn *ir.Func // 对于调试——正在内联的函数

	// 如果为true，则在替换过程中不更新源位置
	// （保留旧的源位置）。
	noPosUpdate bool
}

// list内联节点列表。
func (subst *inlsubst) list(ll ir.Nodes) []ir.Node {
	s := make([]ir.Node, 0, len(ll))
	for _, n := range ll {
		s = append(s, subst.node(n))
	}
	return s
}

// fields在复制字段节点并替换字段节点内的
// 参数或结果的结构类型的字段列表。
// Nname节点后，返回表示接收方、
func (subst *inlsubst) fields(oldt *types.Type) []*types.Field {
	oldfields := oldt.FieldSlice()
	newfields := make([]*types.Field, len(oldfields))
	for i := range oldfields {
		newfields[i] = oldfields[i].Copy()
		if oldfields[i].Nname != nil {
			newfields[i].Nname = subst.node(oldfields[i].Nname.(*ir.Name))
		}
	}
	return newfields
}

// clovar在内联函数中为闭包的局部变量或参数创建一个新的ONAME节点。
func (subst *inlsubst) clovar(n *ir.Name) *ir.Name {
	m := ir.NewNameAt(n.Pos(), n.Sym())
	m.Class = n.Class
	m.SetType(n.Type())
	m.SetTypecheck(1)
	if n.IsClosureVar() {
		m.SetIsClosureVar(true)
	}
	if n.Addrtaken() {
		m.SetAddrtaken(true)
	}
	if n.Used() {
		m.SetUsed(true)
	}
	m.Defn = n.Defn

	m.Curfn = subst.newclofn

	switch defn := n.Defn.(type) {
	case nil:
		// 确定
	case *ir.Name:
		if !n.IsClosureVar() {
			base.FatalfAt(n.Pos(), "want closure variable, got: %+v", n)
		}
		if n.Sym().Pkg != types.LocalPkg {
			// 如果闭包来自
			// 另一个包的函数内联，则必须将捕获的
			// 变量的包更改为localpkg，以便闭包
			// 结构的字段是本地包，即使未导出
			// 名称，也可以访问。如果禁用此代码，可以通过运行“go test”来重现问题。TODO（mdempsky）-可能会更改
			// 我们如何创建闭包结构？
			m.SetSym(types.LocalPkg.Lookup(n.Sym().Name))
		}
		// 确保在内联期间重写ONAME闭包变量的Defn 
		// 的任何内联变量。如果Defn node在内联函数之外，则不要替换
		// 。
		if subst.inlvars[n.Defn.(*ir.Name)] != nil {
			m.Defn = subst.node(n.Defn)
		}
	case *ir.AssignStmt, *ir.AssignListStmt:
		// 在inlsubst末尾标记要重新分配的节点。节点。
		m.Defn = &subst.defnMarker
	case *ir.TypeSwitchGuard:
		// TODO（mdempsky）：正确设置m.Defn。见#45743的讨论。
	case *ir.RangeStmt:
		// TODO:如果我们将来支持内联范围语句，请正确设置m.Defn。
	default:
		base.FatalfAt(n.Pos(), "unexpected Defn: %+v", defn)
	}

	if n.Outer != nil {
		// 要么外部变量是在内联函数中定义的，要么我们将其替换为替换变量，要么是在内联函数外定义的，我们应该跳过外部变量（内联函数
		// 的闭包变量）。
		s := subst.node(n.Outer).(*ir.Name)
		if s == n.Outer {
			s = n.Outer.Outer
		}
		m.Outer = s
	}
	return m
}

// 闭包为ClosureExpr n执行必要的子条件，并返回新的
// 闭包节点。
func (subst *inlsubst) closure(n *ir.ClosureExpr) ir.Node {
	// 在subst编辑之前，在inlsubst中设置一个标志，指示
	// 我们不想在新的
	// 闭包函数中更新源位置。如果我们这样做的话，似乎
	// 闭包本身就包含了一些东西，而不是
	// /案件。有关更多详细信息，请参阅第46234期。同时，我们想更新新ClosureExpr中的位置（这是我们正在处理的函数的一部分）。参见#49171了解
	// 如果我们错过了更新会发生什么的示例。
	newClosurePos := subst.updatedPos(n.Pos())
	defer func(prev bool) { subst.noPosUpdate = prev }(subst.noPosUpdate)
	subst.noPosUpdate = true

	// fmt。Printf（“将函数%v与闭包一起内联到%v\n”，subst.fn，ir.FuncName（ir.CurFunc））

	oldfn := n.Func
	newfn := ir.NewClosureFunc(oldfn.Pos(), true)

	// Ntype在-G=3模式下可以为零。
	if oldfn.Nname.Ntype != nil {
		newfn.Nname.Ntype = subst.node(oldfn.Nname.Ntype).(ir.Ntype)
	}

	if subst.newclofn != nil {
		// fmt。Printf（“用嵌套闭包内联闭包\n”）
	}
	prevxfunc := subst.newclofn

	// 标记我们现在正在闭包内替换（在
	// 内联函数内），并为该闭包内的所有本地
	// vars/params创建新节点。
	subst.newclofn = newfn
	newfn.Dcl = nil
	newfn.ClosureVars = nil
	for _, oldv := range oldfn.Dcl {
		newv := subst.clovar(oldv)
		subst.inlvars[oldv] = newv
		newfn.Dcl = append(newfn.Dcl, newv)
	}
	for _, oldv := range oldfn.ClosureVars {
		newv := subst.clovar(oldv)
		subst.inlvars[oldv] = newv
		newfn.ClosureVars = append(newfn.ClosureVars, newv)
	}

	// 需要替换
	// newfn中的ONAME节点。类型（）。FuncType（）。接收器/参数/结果。FieldSlice（）。Nname 
	oldt := oldfn.Type()
	newrecvs := subst.fields(oldt.Recvs())
	var newrecv *types.Field
	if len(newrecvs) > 0 {
		newrecv = newrecvs[0]
	}
	newt := types.NewSignature(oldt.Pkg(), newrecv,
		nil, subst.fields(oldt.Params()), subst.fields(oldt.Results()))

	newfn.Nname.SetType(newt)
	newfn.Body = subst.list(oldfn.Body)

	// 从subst.inlvars 
	for _, oldv := range oldfn.Dcl {
		delete(subst.inlvars, oldv)
	}
	for _, oldv := range oldfn.ClosureVars {
		delete(subst.inlvars, oldv)
	}
	// 返回到上一个闭包函数
	subst.newclofn = prevxfunc

	// 实际为闭包创建命名函数，现在
	// 闭包内联在特定函数中。
	newclo := newfn.OClosure
	newclo.SetPos(newClosurePos)
	newclo.SetInit(subst.list(n.Init()))
	return typecheck.Expr(newclo)
}

// 节点递归地从
// 内联函数保存的原始体复制一个节点，将输入/输出
// 参数的引用替换为tmpnames的参数，并将返回值替换为输出的
// 赋值。
func (subst *inlsubst) node(n ir.Node) ir.Node {
	if n == nil {
		return nil
	}

	switch n.Op() {
	case ir.ONAME:
		n := n.(*ir.Name)

		// 内联闭包时处理捕获的变量。
		if n.IsClosureVar() && subst.newclofn == nil {
			o := n.Outer

			// 处理闭包序列内联的情况。
			// TODO（danscales）-编写测试用例，看看我们是否需要提升多个级别。
			if o.Curfn != ir.CurFunc {
				o = o.Outer
			}

			// 确保外部参数与内联位置
			if o == nil || o.Curfn != ir.CurFunc {
				base.Fatalf("%v: unresolvable capture %v\n", ir.Line(n), n)
			}

			if base.Flag.LowerM > 2 {
				fmt.Printf("substituting captured name %+v  ->  %+v\n", n, o)
			}
			return o
		}

		if inlvar := subst.inlvars[n]; inlvar != nil { // 这些参数将在内联节点
			if base.Flag.LowerM > 2 {
				fmt.Printf("substituting name %+v  ->  %+v\n", n, inlvar)
			}
			return inlvar
		}

		if base.Flag.LowerM > 2 {
			fmt.Printf("not substituting name %+v\n", n)
		}
		return n

	case ir.OMETHEXPR:
		n := n.(*ir.SelectorExpr)
		return n

	case ir.OLITERAL, ir.ONIL, ir.OTYPE:
		// 期间设置。如果n是命名常量或类型，我们可以在内联副本中继续使用它。否则，请复制
		// 以便我们可以更新行号。
		if n.Sym() != nil {
			return n
		}

	case ir.ORETURN:
		if subst.newclofn != nil {
			// 如果在闭包内，不要进行特殊替换
			break
		}
		// 由于上面对subst.newclofn、
		// 的测试，此返回保证属于当前的内联函数。
		n := n.(*ir.ReturnStmt)
		init := subst.list(n.Init())
		if len(subst.retvars) != 0 && len(n.Results) != 0 {
			as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil)

			// 制作一份浅显的retvars副本。
			// 否则将继续呼叫。Rlist将是相同的列表，
			// 稍后的walk和typecheck可能会将其删除。
			for _, n := range subst.retvars {
				as.Lhs.Append(n)
			}
			as.Rhs = subst.list(n.Results)

			if subst.fn.Inl.CanDelayResults {
				for _, n := range as.Lhs {
					as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, n.(*ir.Name)))
					n.Name().Defn = as
				}
			}

			init = append(init, typecheck.Stmt(as))
		}
		init = append(init, ir.NewBranchStmt(base.Pos, ir.OGOTO, subst.retlabel))
		typecheck.Stmts(init)
		return ir.NewBlockStmt(base.Pos, init)

	case ir.OGOTO, ir.OBREAK, ir.OCONTINUE:
		if subst.newclofn != nil {
			// 如果在闭包内不做特殊替换
			break
		}
		n := n.(*ir.BranchStmt)
		m := ir.Copy(n).(*ir.BranchStmt)
		m.SetPos(subst.updatedPos(m.Pos()))
		m.SetInit(nil)
		m.Label = translateLabel(n.Label)
		return m

	case ir.OLABEL:
		if subst.newclofn != nil {
			// 如果在闭包内不做特殊替换
			break
		}
		n := n.(*ir.LabelStmt)
		m := ir.Copy(n).(*ir.LabelStmt)
		m.SetPos(subst.updatedPos(m.Pos()))
		m.SetInit(nil)
		m.Label = translateLabel(n.Label)
		return m

	case ir.OCLOSURE:
		return subst.closure(n.(*ir.ClosureExpr))

	}

	m := ir.Copy(n)
	m.SetPos(subst.updatedPos(m.Pos()))
	ir.EditChildren(m, subst.edit)

	if subst.newclofn == nil {
		// 为、范围循环或开关转换任何标签
		switch m.Op() {
		case ir.OFOR:
			m := m.(*ir.ForStmt)
			m.Label = translateLabel(m.Label)
			return m

		case ir.ORANGE:
			m := m.(*ir.RangeStmt)
			m.Label = translateLabel(m.Label)
			return m

		case ir.OSWITCH:
			m := m.(*ir.SwitchStmt)
			m.Label = translateLabel(m.Label)
			return m
		}

	}

	switch m := m.(type) {
	case *ir.AssignStmt:
		if lhs, ok := m.X.(*ir.Name); ok && lhs.Defn == &subst.defnMarker {
			lhs.Defn = m
		}
	case *ir.AssignListStmt:
		for _, lhs := range m.Lhs {
			if lhs, ok := lhs.(*ir.Name); ok && lhs.Defn == &subst.defnMarker {
				lhs.Defn = m
			}
		}
	}

	return m
}

// translateLabel通过
// 添加“·inlgen”使内联函数的标签（如果非nil）唯一。
func translateLabel(l *types.Sym) *types.Sym {
	if l == nil {
		return nil
	}
	p := fmt.Sprintf("%s·%d", l.Name, inlgen)
	return typecheck.Lookup(p)
}

func (subst *inlsubst) updatedPos(xpos src.XPos) src.XPos {
	if subst.noPosUpdate {
		return xpos
	}
	pos := base.Ctxt.PosTable.Pos(xpos)
	oldbase := pos.Base() // 可以为零
	newbase := subst.bases[oldbase]
	if newbase == nil {
		newbase = src.NewInliningBase(oldbase, subst.newInlIndex)
		subst.bases[oldbase] = newbase
	}
	pos.SetBase(newbase)
	return base.Ctxt.PosTable.XPos(pos)
}

func pruneUnusedAutos(ll []*ir.Name, vis *hairyVisitor) []*ir.Name {
	s := make([]*ir.Name, 0, len(ll))
	for _, n := range ll {
		if n.Class == ir.PAUTO {
			if !vis.usedLocals.Has(n) {
				continue
			}
		}
		s = append(s, n)
	}
	return s
}

// numNonClosures返回列表中不是闭包的函数数。
func numNonClosures(list []*ir.Func) int {
	count := 0
	for _, fn := range list {
		if fn.OClosure == nil {
			count++
		}
	}
	return count
}

func doList(list []ir.Node, do func(ir.Node) bool) bool {
	for _, x := range list {
		if x != nil {
			if do(x) {
				return true
			}
		}
	}
	return false
}
